<?php
/**
 * Zend Framework
 *
 * LICENSE
 *
 * This source file is subject to the new BSD license that is bundled
 * with this package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://framework.zend.com/license/new-bsd
 * If you did not receive a copy of the license and are unable to
 * obtain it through the world-wide-web, please send an email
 * to license@zend.com so we can send you a copy immediately.
 *
 * @category   Zend
 * @package    Zend_DataGrid
 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */


/** @see Zend_DataGrid_Column */
require_once 'Zend/DataGrid/Column.php';

/** @see Zend_DataGrid_Column_Exception */
require_once 'Zend/DataGrid/Column/Exception.php';

/** @see Zend_DataGrid_Renderer */
require_once 'Zend/DataGrid/Renderer.php';



/**
 * Zend_DataGrid_AssocColumns
 *
 * Associate columns
 *
 * @category   Zend
 * @package    Zend_DataGrid
 * @copyright  Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 * @version    $Id: AssocColumns.php 10406 2008-09-19 15:13:05Z azoungrana $
 */
class Zend_DataGrid_AssocColumns implements Iterator, Countable
{
    /**#@+
     * Plugin loader type constants
     */
    const RENDERER = 'RENDERER';
    const FILTER = 'FILTER';
    const COLUMN = 'COLUMN';
    /**#@-*/

    /**
     * Head and footer Display group Columns
     *
     * @var array
     */
    protected $_columns = array();

    /**
     * The filters to use for the formatting of the column
     *
     * @var array
     */
    protected $_filters = array();

    /**
     * Column footer text
     *
     * @var string
     */
    protected $_footerText;

    /**
     * Column header text
     *
     * @var string
     */
    protected $_headerText;

    /**
     * The loaders
     *
     * @var array
     */
    protected $_loaders = array();

    /**
     * The name (unique id) of the associeted column
     *
     * @var string
     */
    protected $_name;

    /**
     * Order of this column in the collection of columns
     *
     * @var integer
     */
    protected $_order = array();

    /**
     * Whether internal order has been updated or not
     *
     * @var boolean
     */
    protected $_orderUpdated = false;

    /**
     * Renderers objects to use for rendering this column
     */
    protected $_renderers = array();

    /**
     * The datagrid column translator
     *
     * @var Zend_Translate_Adapter
     */
    protected $_translator;

    /**
     * Column visibility. Allow hidden column
     *
     * @var boolean
     */
    protected $_visible;

    /**
     * Constructor
     *
     * $spec may be:
     * - string: name of element
     * - array: options with which to configure element
     * - Zend_Config: Zend_Config with options for configuring element
     *
     * @param  string|array|Zend_Config $spec
     * @param array|Zend_Config $options
     * @return void
     * @throws Zend_DataGrid_Column_Exception if no element name after initialization
     */
    public function __construct($spec, $options = null)
    {
        if (is_string($spec)) {
            $this->setName($spec);
        }

        if (is_array($spec)) {
            $this->setOptions($spec);
        } elseif ($spec instanceof Zend_Config) {
            $this->setConfig($spec);
        } elseif (is_array($options)) {
            $this->setOptions($options);
        } elseif ($options instanceof Zend_Config) {
            $this->setConfig($options);
        }

        //Initialize object; used by extending classes
        $this->init();

        if (null === $this->getName()) {
            require_once 'Zend/DataGrid/Column/Exception.php';
            throw new Zend_DataGrid_Column_Exception('Zend_DataGrid_AssocColumns requires each element to have a name');
        }
    }

    /**
     * Initialize object; used by extending classes
     *
     * @return void
     */
    public function init() {}


    //Option configuration


    /**
     * Sets options
     *
     * @param  array $options
     * @return Zend_DataGrid_AssocColumns
     */
    public function setOptions(array $options)
    {
        foreach ($options as $key => $value) {
            if ($key === 'options') {
                $this->setOptions($value);
                continue;
            }

            if ($key === 'renderer') {
                foreach ($value as $rendererName => $renderOptions) {
                    $this->setRendererOptions($renderOptions, $rendererName);
                }
                continue;
            }

            if ($key === 'prefixPath') {
                $this->addPrefixPaths($value);
                continue;
            }

            $method = 'set' . ucfirst($key);

            if (method_exists($this, $method)) {
                // Setter exists; use it
                $this->$method($value);
            } else {
                // Assume it's renderer metadata
                $this->setRendererOption($key, $value);
            }
        }
        return $this;
    }

    /**
     * Sets options from config object
     *
     * @param  Zend_Config $config
     * @return Zend_DataGrid_AssocColumns
     */
    public function setConfig(Zend_Config $config)
    {
         return $this->setOptions($config->toArray());
    }


    //Attributes and Metadata


    /**
     * Sets column name
     *
     * @param  string $name
     * @return Zend_DataGrid_AssocColumns
     */
    public function setName($name)
    {
        $this->_name = (string) $name;
        return $this;
    }

    /**
     * Return column name
     *
     * @return string
     */
    public function getName()
    {
        return $this->_name;
    }

    /**
     * Set column header text
     *
     * @param  string $name
     * @return Zend_DataGrid_AssocColumns
     */
    public function setHeaderText($name)
    {
        $this->_headerText = (string) $name;
        return $this;
    }

    /**
     * Return column header text
     *
     * @return string
     */
    public function getHeaderText()
    {
        return $this->_headerText;
    }

    /**
     * Sets column footer text
     *
     * @param  string $name
     * @return Zend_DataGrid_AssocColumns
     */
    public function setFooterText($name)
    {
        $this->_footerText = (string) $name;
        return $this;
    }

    /**
     * Return column footer text
     *
     * @return string
     */
    public function getFooterText()
    {
        return $this->_footerText;
    }

    /**
     * Set column order
     *
     * @param  int $order
     * @return Zend_DataGrid_AssocColumns
     */
    public function setOrder($order)
    {
        $this->_order = (int) $order;
        return $this;
    }

    /**
     * Retrieve column order
     *
     * @return int
     */
    public function getOrder()
    {
        return $this->_order;
    }

    /**
     * Sets column visibility
     *
     * @param  boolean $order
     * @return Zend_DataGrid_AssocColumns
     */
    public function setVisible($value)
    {
        $this->_visible = (boolean) $value;
        return $this;
    }

    /**
     * Retrieve column visibility
     *
     * @return boolean
     */
    public function getVisible()
    {
        return $this->_visible;
    }


    // Columns


    /**
     * Add column to Assoc_Columns
     *
     * @param  string|array|Zend_DataGrid_Column $column
     * @return Zend_DataGrid_AssocColumns
     * @throws Zend_DataGrid_Column_Exception if any element is not a Zend_DataGrid_Column
     */
    public function addColumn($column)
    {
        if (is_string($column) or is_array($column)) {
            $column = new Zend_DataGrid_Column($column);
        }

        if(!($column instanceof Zend_DataGrid_Column)) {
            throw new Zend_DataGrid_Column_Exception('$column must be a Zend_DataGrid_Column element');
        }

        $this->_columns[$column->getName()] = $column;

        return $this;
    }

    /**
     * Add multiple columns at once
     *
     * @param  array $columns
     * @return Zend_DataGrid_AssocColumns
     * @throws Zend_DataGrid_Column_Exception if any element is not a Zend_DataGrid_Column
     */
    public function addColumns(array $columns)
    {
        foreach ($columns as $columnInfo)
            $this->addColumn($columnInfo);

        return $this;
    }

    /**
     * Sets multiple columns at once (overwrites)
     *
     * @param  array $columns
     * @return Zend_DataGrid_AssocColumns
     */
    public function setColumns(array $columns)
    {
        $this->clearColumns();
        $this->addColumns($columns);

        return $this;
    }

    /**
     * Retrieve a single column
     *
     * @param  string $name
     * @return null|Zend_DataGrid_Column
     */
    public function getColumn($name)
    {
        if (isset($this->_columns[$name])) {
            return $this->_columns[$name];
        }

        return null;
    }

    /**
     * Retrieve columns
     * @return array
     */
    public function getColumns()
    {
        return $this->_columns;
    }

    /**
     * Remove a single column
     *
     * @param  string $name
     * @return boolean
     */
    public function removeColumn($name)
    {
        if (isset($this->_columns[$name])) {
            unset($this->_columns[$name]);
            return true;
        }

        return false;
    }

    /**
     * Remove all columns
     *
     * @return Zend_DataGrid_AssocColumns
     */
    public function clearColumns()
    {
        $this->_columns = array();
        return $this;
    }

    /**
     * Get the order of columns and associated columns in datagrid
     *
     * @return array
     */
    public function getColumnsOrder()
    {
        if ($this->_orderUpdated) {
            asort($this->_order);
        }
        return $this->_order;
    }


    //Prefix path and loaders

    /**
     * Add prefix path for plugin loader
     *
     * If no $type specified, assumes it is a base path for filters and
     * sets each according to the following rules:
     * - filters: $prefix = $prefix . '_Filter'
     * - renderers: $prefix = $prefix . '_Renderer'
     *
     * Otherwise, the path prefix is set on the appropriate plugin loader.
     *
     * @param string $prefix
     * @param  string $path
     * @param string $type
     * @return Zend_DataGrid_AssocColumns
     * @throws Zend_DataGrid_Column_Exception for invalid type
     */
    public function addPrefixPath($prefix, $path, $type = null)
    {
        $type = strtoupper($type);
        switch ($type) {
            case self::RENDERER:
            case self::FILTER:
            case null:
                $prefix = rtrim($prefix, '_');
                $path   = rtrim($path, DIRECTORY_SEPARATOR);
                foreach (array( self::FILTER) as $type) {
                    $cType        = ucfirst(strtolower($type));
                    $pluginPath   = $path . DIRECTORY_SEPARATOR . $cType . DIRECTORY_SEPARATOR;
                    $pluginPrefix = $prefix . '_' . $cType;
                    $loader       = $this->getPluginLoader($type);
                    $loader->addPrefixPath($pluginPrefix, $pluginPath);
                }
                return $this;

            default:
                require_once 'Zend/DataGrid/Column/Exception.php';
                throw new Zend_DataGrid_Column_Exception(sprintf('Invalid type "%s" provided to getPluginLoader()', $type));
        }
    }

    /**
     * Add many prefix paths at once
     *
     * @param  array $spec
     * @return Zend_DataGrid_AssocColumns
     */
    public function addPrefixPaths(array $spec)
    {
        if (isset($spec['prefix']) && isset($spec['path'])) {
            return $this->addPrefixPath($spec['prefix'], $spec['path']);
        }
        foreach ($spec as $type => $paths) {
            if (is_numeric($type) && is_array($paths)) {
                $type = null;
                if (isset($paths['prefix']) && isset($paths['path'])) {
                    if (isset($paths['type'])) {
                        $type = $paths['type'];
                    }
                    $this->addPrefixPath($paths['prefix'], $paths['path'], $type);
                }
            } elseif (!is_numeric($type)) {
                if (!isset($paths['prefix']) || !isset($paths['path'])) {
                    continue;
                }
                $this->addPrefixPath($paths['prefix'], $paths['path'], $type);
            }
        }
        return $this;
    }


    // Loaders


    /**
     * Set plugin loader to use for validator or filter chain
     *
     * @param  Zend_Loader_PluginLoader_Interface $loader
     * @param  string $type  'filter', 'renderer'
     * @return Zend_DataGrid_AssocColumns
     * @throws Zend_DataGrid_Column_Exception on invalid type
     */
    public function setPluginLoader(Zend_Loader_PluginLoader_Interface $loader, $type)
    {
        $type = strtoupper($type);
        switch ($type) {
            case self::RENDERER:
            case self::FILTER:
                $this->_loaders[$type] = $loader;
                return $this;
            default:
                require_once 'Zend/DataGrid/Column/Exception.php';
                throw new Zend_DataGrid_Column_Exception(sprintf('Invalid type "%s" provided to setPluginLoader()', $type));
        }
    }

    /**
     * Retrieve plugin loader for filter chain
     *
     * Instantiates with default rules if none available for that type. Use
     *  'filter' or 'renderer' for $type.
     *
     * @param  string $type
     * @return Zend_Loader_PluginLoader
     * @throws Zend_Loader_Exception on invalid type.
     */
    public function getPluginLoader($type)
    {
        $type = strtoupper($type);
        switch ($type) {
            case self::RENDERER:
                $prefixSegment = 'DataGrid_Renderer';
                $pathSegment   = 'DataGrid/Renderer';
            case self::FILTER:
                $prefixSegment = ucfirst(strtolower($type));
                $pathSegment   = $prefixSegment;
                if (!isset($prefixSegment)) {
                    $prefixSegment = 'Filter';
                    $pathSegment   = 'Filter';
                }
                if (!isset($this->_loaders[$type])) {
                    require_once 'Zend/Loader/PluginLoader.php';
                    $this->_loaders[$type] = new Zend_Loader_PluginLoader(
                            array('Zend_' . $prefixSegment . '_' => 'Zend/' . $pathSegment . '/')
                    );
                }
                return $this->_loaders[$type];
            default:
                require_once 'Zend/Form/Exception.php';
                throw new Zend_Form_Exception(sprintf('Invalid type "%s" provided to getPluginLoader()', $type));
        }
    }


    //filter


    /**
     * Set the datagrid render filter
     *
     * @param string|Zend_Filter_Interface $filter
     * @return Zend_DataGrid_AssocColumns
     */
    public function setFilter($filter)
    {
        $this->clearFilters();
        $this->addFilter($filter);

        return $this;
    }

    /**
     * Retrieve a single filter by name
     *
     * @param  string $name
     * @return Zend_Filter_Interface
     */
    public function getFilter($name)
    {
        if (!isset($this->_filters[$name])) {
            $filters = array_keys($this->_filters);
            $len = strlen($name);
            foreach ($filters as $filter) {
                if (0 === substr_compare($filter, $name, -$len, $len, true)) {
                    return $this->_filters[$filter];
                }
            }
        }

        return $this->_filters[$name];
    }

    /**
     * Set filters to column, overwriting any already existing
     *
     * @param  array $filters
     * @return Zend_DataGrid_AssocColumns
     */
    public function setFilters(array $filters)
    {
        $this->clearFilters();
        return $this->addFilters($filters);
    }

    /**
     * Get all filters
     *
     * @return array
     */
    public function getFilters()
    {
        return $this->_filters;
    }

    /**
     * Add a filter to the column
     *
     * @param  string|Zend_Filter_Interface $filter
     * @return Zend_DataGrid_AssocColumns
     */
    public function addFilter($filter, $options = array())
    {
        if ($filter instanceof Zend_Filter_Interface) {
            $name = get_class($filter);
        } elseif (is_string($filter)) {
            $name = $this->getPluginLoader(self::FILTER)->load($filter);
            if (empty($options)) {
                $filter = new $name;
            } else {
                $r = new ReflectionClass($name);
                if ($r->hasMethod('__construct')) {
                    $filter = $r->newInstanceArgs($options);
                } else {
                    $filter = $r->newInstance();
                }
            }
        } else {
            require_once 'Zend/DataGrid/Exception.php';
            throw new Zend_DataGrid_Column_Exception('Invalid filter provided to addFilter; must be string or Zend_Filter_Interface');
        }

        $this->_filters[$name] = $filter;

        return $this;
    }

    /**
     * Add filters to column, overwriting any already existing
     *
     * @param  array $filters
     * @return Zend_DataGrid_AssocColumns
     */
    public function addFilters(array $filters)
    {
        foreach ($filters as $filterInfo) {
            if (is_string($filterInfo)) {
                $this->addFilter($filterInfo);
            } elseif ($filterInfo instanceof Zend_Filter_Interface) {
                $this->addFilter($filterInfo);
            } elseif (is_array($filterInfo)) {
                $argc                = count($filterInfo);
                $options             = array();
                if (isset($filterInfo['filter'])) {
                    $filter = $filterInfo['filter'];
                    if (isset($filterInfo['options'])) {
                        $options = $filterInfo['options'];
                    }
                    $this->addFilter($filter, $options);
                } else {
                    switch (true) {
                        case (0 == $argc):
                            break;
                        case (1 <= $argc):
                            $filter  = array_shift($filterInfo);
                        case (2 <= $argc):
                            $options = array_shift($filterInfo);
                        default:
                            $this->addFilter($filter, $options);
                            break;
                    }
                }
            } else {
                require_once 'Zend/DataGrid/Column/Exception.php';
                throw new Zend_DataGrid_Column_Exception('Invalid filter passed to addFilters()');
            }
        }

        return $this;
    }

    /**
     * Remove a filter by name
     *
     * @param  string $name
     * @return boolean
     */
    public function removeFilter($name)
    {
        $filter = $this->getFilter($name);
        if ($filter) {
            $name = get_class($filter);
            unset($this->_filters[$name]);
            return true;
        }

        return false;
    }

    /**
     * Clear all filters
     *
     * @return Zend_DataGrid_AssocColumns
     */
    public function clearFilters()
    {
        $this->_filters = array();
        return $this;
    }

    /**
     * Filter the content
     *
     * @param string $content
     * @return string
     */
    public function filter($content)
    {
        foreach($this->_filters as $filter)
            $content = $filter->filter($content);

        return $content;
    }


    // Localization


    /**
     * Set translator object for localization
     *
     * @param  Zend_Translate_Adapter $translator
     * @return Zend_DataGrid_AssocColumns
     */
    public function setTranslator(Zend_Translate_Adapter $translator = null)
    {
        $this->_translator = $translator;
        return $this;
    }

    /**
     * Retrieve localization translator object
     *
     * @return Zend_Translate_Adapter
     */
    public function getTranslator()
    {
        require_once 'Zend/Translate/Adapter.php';

        return $this->_translator;
    }


    //Renderer and Renderer options


    /**
     * Sets the renderer object
     *
     * @param string|Zend_DataGrid_Renderer $type
     * @param array|Zend_Config $options
     * @return Zend_DataGrid_AssocColumns
     * @throws Zend_DataGrid_Renderer_Exception when the renderer not a class of Zend_DataGrid_Renderer
     */
    public function setRenderer($type, $options = null)
    {
        if (is_string($type)) {
            $renderer = Zend_DataGrid_Renderer::factory($type, $this);
        } else if ($type instanceof Zend_DataGrid_Renderer) {
            $renderer = $type;
            $type = $renderer->getType();
            $renderer->setGridElement($this);
        }

        if (!($renderer instanceof Zend_DataGrid_Renderer))
            throw new Zend_DataGrid_Renderer_Exception('Must be an instance of Zend_DataGrid_Renderer_Abstract');

        if (null !== $options) {
            $renderer->setOptions($options);
        }

        $this->_renderers[$type] = $renderer;

        return $this;
    }

    /**
     * Get renderer object
     * The renderer will be create if not exist
     *
     * @param string $type
     * @return Zend_DataGrid_Renderer
     */
    public function getRenderer($type = null)
    {
        if (null === $type) {
            $type = Zend_DataGrid_Renderer::getDefaultType();
        }

        if (! array_key_exists($type, $this->_renderers)) {
            $renderer = Zend_DataGrid_Renderer::factory($type, $this);
            $this->setRenderer($renderer);
        }

        return $this->_renderers[$type];
    }

    /**
     * Set the assoc column renderer option
     *
     * @param  string $name
     * @param  string $value
     * @param  string $rendererType
     * @return Zend_DataGrid_AssocColumns
     */
    public function setRendererOption($name, $value, $rendererType = null)
    {
        $renderer = $this->getRenderer($rendererType);

        $method = 'set' . ucfirst($name);

        if (method_exists($renderer, $method)) {
            $renderer->$method($value);
        } else {
            $renderer->setOption($key, $value);
        }

        return $this;
    }

    /**
     * Get the assoc column renderer option
     *
     * @param string $name
     * @param string $rendererType
     * @return mixed
     */
    public function getRendererOption($name, $rendererType = null)
    {
        $renderer = $this->getRenderer($rendererType);

        $method = 'get' . ucfirst($name);

        if (method_exists($renderer, $method)) {
            return $renderer->$method();
        } else {
            return $renderer->getOption($name);
        }
    }

    /**
     * Set the assoc column renderer options
     *
     * @param array $options
     * @param string $rendererType
     * @return Zend_DataGrid_AssocColumns
     */
    public function setRendererOptions($options, $rendererType = null)
    {
        $this->getRenderer($rendererType)->setOptions($options);

        return $this;
    }

    /**
     * Get the assoc column renderer options
     *
     * @param string $rendererType
     * @return array
     */
    public function getRendererOptions($rendererType = null)
    {
        return $this->getRenderer($rendererType)->getOptions();
    }


    //PHP Magic


    /**
     * Overloading: access to columns and renderers
     *
     * @param  string $name
     * @return Zend_DataGrid_Column|Zend_DataGrid_Renderer|null
     */
    public function __get($name)
    {
        $name = (string) $name;

        if (isset($this->_columns[$name])) {
            return $this->getColumn($name);
        } elseif (isset($this->_renderers[$name])) {
            return $this->getRenderer($name);
        }

        return null;
    }

    /**
     * Overloading: access to columns and renderers
     *
     * @param  string $name
     * @param  Zend_DataGrid_Column|Zend_DataGrid_Renderer $value
     * @return void
     * @throws Zend_DataGrid_Column_Exception for invalid $value
     */
    public function __set($name, $value)
    {
        if ($value instanceof Zend_DataGrid_Column) {
            $this->addColumn($value, $name);
            return $this;
        } elseif ($value instanceof Zend_DataGrid_Renderer) {
            $this->setRenderer($value);
            return $this;
        }

        require_once 'Zend/DataGrid/Column/Exception.php';
        if (is_object($value)) {
            $type = get_class($value);
        } else {
            $type = gettype($value);
        }
        throw new Zend_DataGrid_Column_Exception('Only assocColumn colums and renderers  may be overloaded; variable of type "' . $type . '" provided');
    }

    /**
     * Overloading: access to columns and renderers
     *
     * @param  string $name
     * @return boolean
     */
    public function __isset($name)
    {
        if (isset($this->_columns[$name]) || isset($this->_renderers[$name]))
        {
            return true;
        }

        return false;
    }

    /**
     * Overloading: access to columns, assoc columns, and renderers
     *
     * @param  string $name
     * @return void
     */
    public function __unset($name)
    {
        if (isset($this->_columns[$name])) {
            $this->removeColumn($name);
        } elseif (isset($this->_renderers[$name])) {
            unset($this->_renderers[$name]);
        }
    }

    /**
     * Overloading: allow get/set specific renderers options
     *
     * Call the renderer to personnalize it.
     *
     * @param  string $method
     * @param  array $args
     * @return string
     * @throws Zend_DataGrid_Column_Exception for invalid renderer or invalid method call
     */
    public function __call($method, $args)
    {
        if (!method_exists($this->getRenderer(), $method)) {
            require_once 'Zend/DataGrid/Column/Exception.php';
            throw new Zend_DataGrid_Column_Exception(sprintf('Method %s does not exist', $method));
        }

        $seed = $value = null;
        if (0 < count($args)) {
            $seed = array_shift($args);
            $value = array_shift($args);
        }

        if (null === $seed) {
            return $this->getRenderer()->$method();
        } else {
            $this->getRenderer()->$method($seed, $value);
            return $this;
        }
    }


    // Interfaces: Iterator, Countable


    /**
     * Current column
     *
     * @return Zend_DataGrid_Column
     */
    public function current()
    {
        $this->_sort();
        current($this->_order);
        $key = key($this->_order);

        if (isset($this->_columns[$key])) {
            return $this->getColumn($key);
        } else {
            require_once 'Zend/DataGrid/Column/Exception.php';
            throw new Zend_DataGrid_Column_Exception(sprintf('Corruption detected in assoc column; invalid key ("%s") found in internal iterator', (string) $key));
        }
    }

    /**
     * Current column
     *
     * @return string
     */
    public function key()
    {
        $this->_sort();
        return key($this->_order);
    }

    /**
     * Move pointer to next column
     *
     * @return void
     */
    public function next()
    {
        $this->_sort();
        next($this->_order);
    }

    /**
     * Move pointer to beginning of column loop
     *
     * @return void
     */
    public function rewind()
    {
        $this->_sort();
        reset($this->_order);
    }

    /**
     * Determine if current column is valid
     *
     * @return bool
     */
    public function valid()
    {
        $this->_sort();
        return (current($this->_order) !== false);
    }

    /**
     * Count of column that are iterable
     *
     * @return int
     */
    public function count()
    {
        return count($this->_order);
    }



}
